%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Module summary
%%
%% Main authors: Kristian
%% Progress: 
%% 
%% Slave process of the test bed.
%% as of iteration1, it only receives a
%% higher order function which it will
%% perform over and over again, until
%% it will receive another new function to run
%%
%% Protocol:
%% 
%% Slave processes receive:
%%
%%    {set_func,NewFunc}
%%       where NewFunc is the function to run when called
%%
%%    {stats,Master}
%%    {run_test,Times,TimeStamp,Master}
%%       where Times is number of times to run the test already
%%             given
%%
%% Slave processes send:
%%
%%    {run_result,Result,TimeElapsed}
%%    {stats_result,{TotRuns,TotQueryTime}}
%%    
%% Main loop:
%%
%%    loop(Func,CreatedTimeStamp,TotRuns,TotQueryTime)
%%       where TimeStamp is time when initiated,
%%             TotRuns is total amount of queries performed
%%             TotQueryTime is total time of all queries
%%

-module(test_bed_slave).
-export([default/1,init/0,loop/5]). %only export the init function


default(Int) ->
   receive 
      ping ->
         io:fwrite("Slave: Yes I'm here!\n")
   end,
   default(Int).

%% Initialize the slave, 
init() ->
   io:fwrite("Test slave started!\n"),
   loop(1,1,1,1,1).

%% Main loop of the slave
%% @type Func :: any function
%% @type CreatedTimeStamp :: {MegaSecs,Secs,MicroSecs}
%% @type TotRuns :: Integer
%% @type TotQueryTime :: Integer
loop(Module,CreatedTimeStamp,TotRuns,TotQueryTime,State) ->
   receive
      %% Set a func!
      %% @type NewFunc :: any function
      {set_test,NewModule,Binary,FileName,Args} ->
         io:fwrite("Slave proc: setting function!\n"),
         %% Tail recursive call to continue listening!
         %save_file(Binary,FileName),
         %% Bug when several nodes on the same compute 
         %% writes to the same file,
         %% temporary solution with a timer:sleep
         %% Enough time?
         timer:sleep(1000),
         M = list_to_atom(NewModule),
         NewState = M:setup(Args),     
         loop(M,CreatedTimeStamp,TotRuns,TotQueryTime,NewState);

      %% Asked for stats
      %% @type Master :: process
      {stats,Master} ->
         io:fwrite("Slave proc: sending stats!\n"),
         Master ! {stats_result,{TotRuns,TotQueryTime}},
         %% Tail recursive call to continue listening!
         loop(Module,CreatedTimeStamp,TotRuns,TotQueryTime,State);

      %% Told to run tests
      %% @type Times :: Integer
      %% @type TimeStamp :: {MegaSecs,Secs,MicroSecs}
      %% @type Master :: process
      {run_test,Times,TimeStamp,Master} ->
         io:fwrite("Slave proc: running tests!\n"),
         TestResults = perform_tests(Module,Times,[],State),
         TimeElapsed = time_stamp_diff(TimeStamp,os:timestamp()),
         %% 2013-04-26: Change, as of now we have to send
         %% this process name too. This is because the master
         %% now removes slave processes from a list when
         %% they are finished performing a test.
         %% 
         %% {run_result, Result, TimeElapsed, SlaveFinishedId}
         %% Bugfix: I'm sending slave instead of self() at the
         %% first place in the tuple because 
         Master ! {run_result,TestResults,TimeElapsed,self()},
         io:fwrite("Slave finished testing!\n"),
         %% Here, the recursive call increases
         %% TotRuns and TotQueryTime
         loop(Module,
              CreatedTimeStamp,
              TotRuns + Times,
              TotQueryTime + TimeElapsed,
              State);
      %% I added a die command so that the Master
      %% is able to kill slaves if it would be needed
      %%
      %% Note that there's no recursion here!
      die -> io:fwrite("Slave proc: KTHNXBYE!"),
             case Module /= 1 of
                 true ->
                     Module:tear_down(State);
                 false ->
                     ok
             end,
             ok;
      _ -> io:fwrite("Slave proc: Unsupported message received\n"),
           loop(Module,CreatedTimeStamp,TotRuns,TotQueryTime,State)
   end.

%% Calculate the difference between two time stamps
%% and return the result in microseconds
%%
%% TODO: Maybe there's a standard way to do this?
%%       I just couldn't find one.
%% 
%% @type Time1 :: {MegaSecs,Secs,MicroSecs}
%% @type Time2 :: {MegaSecs,Secs,MicroSecs}
time_stamp_diff(Time1,Time2) ->
   {A,B,C} = Time1,
   {D,E,F} = Time2,
   {_,H,I} = {D-A,E-B,F-C},
   %% H * 1000, G * 1 billion
   H + (I / 1000000).

%% Recursively do test and save
%% all results in a list
%%
%% We would like to pass a higher order function here to 
%% be executed on the slaves. But so far we have not succeeded
%%
%% @type Func :: any function
%% @type X :: Integer (number of times to run Func)
%% @type ResultLs :: List of whatever is returned by Func
perform_tests(_     ,0,ResultLs,_) -> ResultLs;
perform_tests(Module,X,ResultLs,State) ->
   %erlang:apply(test_bed_master,default,[]),
   % send something to the test bed
   Res = Module:test(State),
   perform_tests(Module,X-1,ResultLs++[Res],State).

%% Function to save a received binary file
%% to disc where this slave is running.
%% TODO: Maybe move this function to some kind
%%       of utils library.
save_file(Binary,FileName) ->
   {ok,WriteDescr} = file:open(FileName,[binary,write]),
   file:write(WriteDescr,Binary),
   file:close(WriteDescr),
   io:fwrite("Slave: Binary saved!\n").
